using System;
using System.Collections.Generic;
using System.Text;
using System.Runtime.InteropServices;
using System.Collections;
using System.Windows.Forms;
using System.Threading;

namespace Lizk.Utilities
{
    /// <summary>
    /// Class to register Global Hotkeys.
    /// </summary>
    public class GlobalHotkey
    {
        /// <summary>
        /// Modifiers to use with a hotkey. Includes Alt, Control, Shift and the windows Key
        /// </summary>
        [Flags()]
        public enum Modifiers
        {
            /// <summary>
            /// The Alt keys.
            /// </summary>
            Alt = 1,
            /// <summary>
            /// The Control keys.
            /// </summary>
            Control = 2,
            /// <summary>
            /// The Shift keys.
            /// </summary>
            Shift = 4,
            /// <summary>
            /// The Windows Logo keys.
            /// </summary>
            WinKey = 8
        }
        

        private static Hashtable hotkeyIDs = new Hashtable();
        private static bool registered = false;
        // Windows API functions and constants
        [DllImport("user32", SetLastError = true)]
        private static extern int RegisterHotKey(IntPtr hwnd, int id, int fsModifiers, int vk);
        [DllImport("user32", SetLastError = true)]
        private static extern int UnregisterHotKey(IntPtr hwnd, int id);
        [DllImport("kernel32", SetLastError = true)]
        private static extern short GlobalAddAtom(string lpString);
        [DllImport("kernel32", SetLastError = true)]
        private static extern short GlobalDeleteAtom(short nAtom);

        private struct hotkeyInfo
        {
            public IntPtr windowHandle;
            public short hotkeyID;

            public hotkeyInfo(IntPtr windowHandle, short hotkeyID)
            {
                this.hotkeyID = hotkeyID;
                this.windowHandle = windowHandle;
            }
        }

        /// <summary>
        /// Registers a global hotkey.
        /// </summary>
        /// <param name="window">The window to send hotkey events to</param>
        /// <param name="hotkey">The hotkey to register.</param>
        /// <param name="modifiers">The modifiers the hotkey uses.</param>
        public static void RegisterGlobalHotKey(Form window, Keys hotkey, Modifiers modifiers)
        {
            short hotkeyID = 0;
            try
            {
                // use the GlobalAddAtom API to get a unique ID (as suggested by MSDN docs)
                string atomName = Thread.CurrentThread.ManagedThreadId.ToString("X8") + window.Name + (int)hotkey + "" + (int)modifiers;
                if (hotkeyIDs.Contains(atomName))
                {
                    hotkeyID = ((hotkeyInfo)hotkeyIDs[atomName]).hotkeyID;
                }
                else
                {
                    hotkeyID = GlobalAddAtom(atomName);
                    hotkeyIDs.Add(atomName, new hotkeyInfo(window.Handle, hotkeyID));
                }

                
                if (hotkeyID == 0)
                {
                    throw new Exception("Unable to generate unique hotkey ID. Error code: " + Marshal.GetLastWin32Error().ToString());
                }

                // register the hotkey, throw if any error
                if (RegisterHotKey(window.Handle, hotkeyID, (int)modifiers, (int)hotkey) == 0)
                {
                    throw new Exception("Unable to register hotkey. Error code: " + Marshal.GetLastWin32Error().ToString());
                }
                if (!registered)
                {
                    Application.ApplicationExit += new EventHandler(Application_ApplicationExit);
                    registered = true;
                }
            }
            catch(Exception e)
            {
                // clean up if hotkey registration failed
                UnregisterGlobalHotKey(window.Handle, hotkeyID);
            }
        }

        /// <summary>
        /// Unregisters all Global Hotkeys created using this class.
        /// </summary>
        public static void UnregisterAllGlobalHotkeys()
        {
            foreach (hotkeyInfo hi in hotkeyIDs.Values)
            {
                try
                {
                    UnregisterGlobalHotKey(hi.windowHandle, hi.hotkeyID);
                }
                catch 
                {
                }
            }
        }

        private static void Application_ApplicationExit(object sender, EventArgs e)
        {
            UnregisterAllGlobalHotkeys();
        }
        /// <summary>
        /// Unregisters all hotkeys that has been registered using this class and which are bound the the Form provided.
        /// </summary>
        /// <param name="window">The form whose hotkeys need to be unregistered.</param>
        public static void UnregisterAllGlobalHotkeys(Form window)
        {
            foreach (hotkeyInfo hi in hotkeyIDs.Values)
            {
                try
                {
                    if(window.Handle == hi.windowHandle)
                        UnregisterGlobalHotKey(hi.windowHandle, hi.hotkeyID);
                }
                catch
                {
                }
            }
        }

        // unregister a global hotkey
        private static void UnregisterGlobalHotKey(IntPtr window, short hotkeyID)
        {
            if (hotkeyID != 0)
            {
                UnregisterHotKey(window, hotkeyID);
                // clean up the atom list
                GlobalDeleteAtom(hotkeyID);
            }
        }
        /// <summary>
        /// Parses a Windows Hotkey message and returns the key that was pressed
        /// </summary>
        /// <param name="m">Message to parse</param>
        /// <returns>Key that was pressed</returns>
        public static Keys GetKeyFromMessage(Message m)
        {
            return (Keys)HiWord(m.LParam.ToInt32());
        }
        /// <summary>
        /// Parses a windows hotkey message (WM_HOTKEY) and returns the modifiers pressed.
        /// </summary>
        /// <param name="m">The windows message to parse</param>
        /// <returns>Flags representing the modifiers pressed</returns>
        public static Modifiers GetModifiersFromMessage(Message m)
        {
            return (Modifiers)LoWord(m.LParam.ToInt32());
        }

        private static int HiWord(int number)
        {
            if ((number & 0x80000000) == 0x80000000)
                return (number >> 16);
            else
                return (number >> 16) & 0xffff;
        }

        private static int LoWord(int number)
        {
            return number & 0xffff;
        }

        private static int MakeLong(int LoWord, int HiWord)
        {
            return (HiWord << 16) | (LoWord & 0xffff);
        }

        private static IntPtr MakeLParam(int LoWord, int HiWord)
        {
            return (IntPtr)((HiWord << 16) | (LoWord & 0xffff));
        }

    }
}
